Dans un récent post, je proposais des fonctions basiques pour transformer une sortie lm() en table de résultats. D’autres ont déjà fait le travail avant moi, et bien mieux. C’est donc une bonne raison pour présenter le package modelsummary.
Date de publication
3 février 2026
Point de départ
Dans un récent billet, je montrais comment automatiser l’extraction de coefficients d’un modèle lm(). Je trouve que c’est toujours bien de travailler à la main : manipuler les données, comprendre comment elles sont construites, extraites, comment la table est organisée puis rendue, etc. Cela permet de comprendre ce qui se passe sous le capôt de la machine, et de gagner en autonomie si un package n’existe pas déjà pour faire ce que l’on souhaite faire.
Mais, si l’on est honnête, mes fonctions étaient fragiles et la façon de faire peu élégante.
Aussi, ces fonctions étaient bornées à lm(). Un glm(), ordinal()(Christensen, 2025), ou autre glm.nb()(Venables & Ripley, 2002) auraient renvoyé une erreur, et il aurait fallu construire d’autres fonctions pour en extraire les résultats. Il se trouve que d’autres ont déjà fait ce travail de construction, notamment pour le package modelsummary(Arel-Bundock, 2022) !
Je repars du même jeu de données penguins(Horst et al., 2020) et du même modèle linéaire :
où \[
X_{1}=\mathbb{1}(\text{espèce}_i=\text{Jugulaire});\quad
X_{2}=\mathbb{1}(\text{espèce}_i=\text{Papou});\quad
X_{3}=\mathbb{1}(\text{sexe}_i=\text{Mâle}).
\]
A nouveau, la sortie en base R n’est pas très élégante.
Call:
lm(formula = masse ~ espece + sexe, data = df)
Residuals:
Min 1Q Median 3Q Max
-816.87 -217.80 -16.87 227.61 882.20
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 3372.39 31.43 107.308 <2e-16 ***
especeJugulaire 26.92 46.48 0.579 0.563
especePapou 1377.86 39.10 35.236 <2e-16 ***
sexeMâle 667.56 34.70 19.236 <2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 316.6 on 329 degrees of freedom
(11 observations effacées parce que manquantes)
Multiple R-squared: 0.8468, Adjusted R-squared: 0.8454
F-statistic: 606.1 on 3 and 329 DF, p-value: < 2.2e-16
Table 1: Régression de la masse des manchots en grammes, sur l’espèce et le sexe, selon l’Équation 1. Sortie base R.
La magie de modelsummary::modelsummary()
modelsummary() va être très pratique pour un très grand nombre de raisons :
Supporte énormément de modèles, inférentiels ou bayésiens, des plus obscurs aux plus importants (comme fixest, Bergé et al., 2026).
Directement combinable avec des erreurs personnalisées, du type robustes, clusterisées, etc, via sandwich(Zeileis et al., 2020).
Largement personnalisable, comme on va le voir.
Les tables sont élégantes.
Dans sa version la plus basique, la fonction renvoie une table de régression comme on en voit tant.
Voir le code
modelsummary(lm_1)
(1)
(Intercept)
3372.387
(31.427)
especeJugulaire
26.924
(46.483)
especePapou
1377.858
(39.104)
sexeMâle
667.555
(34.704)
Num.Obs.
333
R2
0.847
R2 Adj.
0.845
AIC
4785.6
BIC
4804.6
Log.Lik.
-2387.797
RMSE
314.70
Table 2: Régression de la masse des manchots en grammes, sur l’espèce et le sexe, selon l’Équation 1. Sortie basique de modelsummary().
En soit, c’est tout ce dont on a besoin : à partir de l’estimation et de son erreur, on peut approximer t, p, et l’intervalle de confiance à 95 %. Mais, l’on pourrait tout de même souhaiter afficher les véritables statistiques plutôt que de devoir les approximer.
J’ajoute donc les statistiques suivantes, alignées dans cet ordre dans la Table 3 :
Estimation.
Intervalles de confiance à 95 %.
Erreur standard.
Statistique t.
Statistique p.
Voir le code
modelsummary(lm_1,estimate =c("Estimation <br> [IC 95 %]"="{estimate}<br>[{conf.low}; {conf.high}]"), # Ajouter l'intervalle de confiance à l'estimatestatistic =c("Erreur standard"="std.error", "Statistique t"="statistic", "p-valeur"="p.value")) # Ajouter la statistique t et la p-valeur
(1)
(Intercept)
3372.387 [3310.563; 3434.210]
(31.427)
(107.308)
(<0.001)
especeJugulaire
26.924 [-64.518; 118.366]
(46.483)
(0.579)
(0.563)
especePapou
1377.858 [1300.932; 1454.784]
(39.104)
(35.236)
(<0.001)
sexeMâle
667.555 [599.285; 735.825]
(34.704)
(19.236)
(<0.001)
Num.Obs.
333
R2
0.847
R2 Adj.
0.845
AIC
4785.6
BIC
4804.6
Log.Lik.
-2387.797
RMSE
314.70
Table 3: Régression de la masse des manchots en grammes, sur l’espèce et le sexe, selon l’Équation 1. Sortie modelsummary() avec statistiques.
Ici, ça se complique : les statistiques s’empilent, la table perd en lisibilité. On va devoir la faire pivoter, pour représenter les mêmes statistiques en colonne plutôt qu’en ligne.
Voir le code
modelsummary(lm_1,estimate =c("Estimation <br> [IC 95 %]"="{estimate}<br>[{conf.low}; {conf.high}]"),statistic =c("Erreur standard"="std.error", "Statistique t"="statistic", "p-valeur"="p.value"),shape = term ~ statistic) # Pivoter la table
(1)
Estimation [IC 95 %]
Erreur standard
Statistique t
p-valeur
(Intercept)
3372.387 [3310.563; 3434.210]
31.427
107.308
<0.001
especeJugulaire
26.924 [-64.518; 118.366]
46.483
0.579
0.563
especePapou
1377.858 [1300.932; 1454.784]
39.104
35.236
<0.001
sexeMâle
667.555 [599.285; 735.825]
34.704
19.236
<0.001
Num.Obs.
333
R2
0.847
R2 Adj.
0.845
AIC
4785.6
BIC
4804.6
Log.Lik.
-2387.797
RMSE
314.70
Table 4: Régression de la masse des manchots en grammes, sur l’espèce et le sexe, selon l’Équation 1. Sortie modelsummary() avec statistiques pivotées.
Cette fois, la table commence à être exploitable. Je finalise l’aspect cosmétique, en :
Renommant les coefficients.
Retirant l’ensemble des indices d’ajustement, en renvoyant uniquement le nombre d’observations et le R² ajusté en une note de bas de tableau.
Retirant le numéro de modèle (1).
Mettant les titres de colonnes en gras et les statistiques en italique.
Formattant les statistiques à deux décimales, et la p-valeur selon les normes APA.
Ajoutant le nombre de degrés de liberté à la colonne t
Voir le code
g <- broom::glance(lm_1) # Stocker les indices d'ajustementddl <-unique(get_estimates(lm_1)$df.error) # Stocker les degrés de libertémodelsummary(shape = term ~ statistic,estimate =c("<strong>Estimation<br>[IC 95 %]</strong>"="{estimate}<br>[{conf.low}; {conf.high}]"), # <strong> pour mettre en gras et <em> pour mettre en italiquestatistic =setNames(c("std.error", "statistic", "p.value"),c("<strong>Erreur standard</strong>",paste0("<strong>Statistique <em>t</em> (", ddl, ")</strong>"), # On vient ici ajouter les degrés de liberté à la colonne"<strong><em>p</em>-valeur</strong>")), models =list(" "= lm_1), # Supprimer le (1)gof_omit =".*", # Supprimer les indices d'ajustementnotes = (paste0("<em>Note</em> : <em>n</em> = ", scales::number(g$nobs), " observations. <em>R²</em> ajusté = ", sprintf("%.2f", g$adj.r.squared),".")), # Ajouter les indices d'ajustement en note de bas de tableaucoef_rename =c("especeJugulaire"="Espèce : Jugulaire","especePapou"="Espèce : Papou","sexeMâle"="Sexe : Mâle"), # Renommer les coefficients fmt =fmt_statistic(estimate =2, std.error =2, statistic =2, conf.low =2, conf.high =2, p.value = scales::label_pvalue())) # Formatter les décimales
Estimation [IC 95 %]
Erreur standard
Statistique t (329)
p-valeur
Note : n = 333 observations. R² ajusté = 0.85.
(Intercept)
3372.39 [3310.56; 3434.21]
31.43
107.31
<0.001
Espèce : Jugulaire
26.92 [-64.52; 118.37]
46.48
0.58
0.563
Espèce : Papou
1377.86 [1300.93; 1454.78]
39.10
35.24
<0.001
Sexe : Mâle
667.56 [599.29; 735.82]
34.70
19.24
<0.001
Table 5: Régression de la masse des manchots en grammes, sur l’espèce et le sexe, selon l’Équation 1. Sortie modelsummary() avec statistiques cosmétiques.
Construire une fonction personnalisée et simple, regroupant tous les besoins classiques en psychologie : fr_modelsummary_lm()
Il ne manque finalement qu’une chose à cette table : d’inclure un coefficient standardisé \(\beta\). Cela demande un peu plus de travail car, de base, modelsummary() ne renvoie pas de coefficient standardisé. On va donc devoir (à nouveau) créer une table personnalisée avant de pouvoir l’envoyer à la fonction.
Plutôt que de devoir à chaque fois ré-écrire ces scripts, et potentiellement devoir les mettre à jour en cas de changement, je construits une fonction pour (1) extraire une table personnalisée et (2) formater cette table personnalisée. Je m’appuie encore une fois sur le package parameters(Lüdecke et al., 2020) pour simplifier l’importation d’une matrice de variance-covariance facilement personnalisable1.
Comparées au post précédent, ces fonctions sont directement intégrées l’une à l’autre et permettent d’appeler une seule fonction personnalisée fr_modelsummary_lm() (ou son équivalent anglophone en_modelsummary_lm(), que je vous mets plus bas). Elles acceptent également un argument vcov. Finalement, elles seraient facilement personnalisables et adaptables à d’autres modèles avec quelques ajustements.
Voir le code
# Fonction frfr_modelsummary_lm <-function( model, vcov =NULL,coef_rename =NULL, gof_omit =".*", ...) {# Vérifier que le modèle est bien de type lm()if (!inherits(model, "lm")) {stop("Le modèle fournit doit être un objet de classe lm. Ce n'est pas le cas ici.",call. =FALSE) }# Première fonction : Extraire une table custom_modelsummary_lm <-function(model, standardize ="refit", vcov =NULL, ...) { est <- modelsummary::get_estimates(model, vcov = vcov) # Extraire les coefficients pour la table de base est_std <- parameters::model_parameters(model, standardize = standardize, vcov = vcov) %>%# Extraire un coefficient standardisé dplyr::select(Parameter, Coefficient, CI_low, CI_high) %>%# Sélectionner les colonnes standardisées dplyr::rename(Std_Coefficient = Coefficient) # Renommer la colonne qui nous intéresse est$estimate_std <-paste0(sprintf("%.2f", est_std$Std_Coefficient), "<br>","[", sprintf("%.2f", est_std$CI_low), "; ", sprintf("%.2f", est_std$CI_high), "]") # Formatter les statistiques à deux décimales out <-list(tidy = est,glance = broom::glance(model)) # Stocker la table et les indices de fitclass(out) <-"modelsummary_list"# Définir la classe de l'objet de telle façon à ce que modelsummary() l'accepte out }# Seconde fonction : Formatter la table est <-custom_modelsummary_lm(model, vcov = vcov) # Appliquer la première fonction au modèle g <- broom::glance(model) # Stocker les indices d'ajustement ddl <-unique(modelsummary::get_estimates(model, vcov = vcov)$df.error) # Stocker les degrés de liberté vcov_note <-if (!is.null(vcov)) paste0(" Erreurs standards : <em>", vcov, "</em>.") else"" notes_txt <-paste0("<em>Note</em> : <em>n</em> = ", scales::number(g$nobs),". <em>R²</em> ajusté = ",sprintf("%.2f", g$adj.r.squared),".", vcov_note)modelsummary(coef_rename = coef_rename,gof_omit = gof_omit,shape = term ~ statistic, estimate =c("<strong>Estimation<br>[IC 95 %]</strong>"="{estimate}<br>[{conf.low}; {conf.high}]"),statistic =setNames(c("std.error", "statistic", "p.value", "estimate_std"), # Ajouter la colonne standardiséec("<strong>Erreur standard</strong>",paste0("<strong>Statistique <em>t</em> (", ddl, ")</strong>"),"<strong><em>p</em>-valeur</strong>","<strong><em>β</em><br>[IC 95 %]</strong>")), # Renommemodels =list(" "= est),notes = notes_txt,fmt =fmt_statistic(estimate =2, std.error =2, statistic =2, conf.low =2, conf.high =2, p.value = scales::label_pvalue()), ...)}
Ces fonctions personnalisables s’appuient ultimement sur modelsummary() et tout argument supplémentaire lui sera donc passé. Un exemple en français via fr_modelsummary_lm(), en intégrant une matrice de variance-covariance robuste à l’hétéroscédasticité.
Table 6: Régression de la masse des manchots en grammes, sur l’espèce et le sexe, selon l’Équation 1. Sortie fr_modelsummary_lm().
La fonction en_modelsummary_lm()
Pour celles et ceux qui pourraient en avoir besoin en anglais, la fonction traduite et le même exemple en appelant cette fois la fonction en_modelsummary_lm() :
Voir le code
# English functionen_modelsummary_lm <-function( model, vcov =NULL,coef_rename =NULL, gof_omit =".*", ...) {# Check that the model is of type lm()if (!inherits(model, "lm")) {stop("The model must be an object of class lm. This is not the case here.",call. =FALSE) }# First function: Extract a table custom_modelsummary_lm <-function(model, standardize ="refit", vcov =NULL, ...) { est <- modelsummary::get_estimates(model, vcov = vcov) # Extract coefficients for the base table est_std <- parameters::model_parameters(model, standardize = standardize, vcov = vcov) %>%# Extract standardised coefficients dplyr::select(Parameter, Coefficient, CI_low, CI_high) %>%# Select standardised columns dplyr::rename(Std_Coefficient = Coefficient) # Rename the column we are interested in est$estimate_std <-paste0(sprintf("%.2f", est_std$Std_Coefficient), "<br>","[", sprintf("%.2f", est_std$CI_low), "; ", sprintf("%.2f", est_std$CI_high), "]") # Format statistics to two decimal places out <-list(tidy = est,glance = broom::glance(model)) # Storing the table and fit indicesclass(out) <-"modelsummary_list"# Define the class of the object so that modelsummary() accepts it out }# Second function: Format the table est <-custom_modelsummary_lm(model, vcov = vcov) # Apply the first function to the model g <- broom::glance(model) # Stock fit indices ddl <-unique(modelsummary::get_estimates(model, vcov = vcov)$df.error) # Stock degrees of freedom vcov_note <-if (!is.null(vcov)) paste0(" Standard errors : <em>", vcov, "</em>.") else"" notes_txt <-paste0("<em>Note</em> : <em>n</em> = ", scales::number(g$nobs),". Adjusted <em>R²</em> = ", sprintf("%.2f", g$adj.r.squared),".", vcov_note)modelsummary(coef_rename = coef_rename,gof_omit = gof_omit,shape = term ~ statistic, estimate =c("<strong>Estimate<br>[95% CI]</strong>"="{estimate}<br>[{conf.low}; {conf.high}]"),statistic =setNames(c("std.error", "statistic", "p.value", "estimate_std"), # Add standardised columnc("<strong>Standard error</strong>",paste0("<strong><em>t</em> statistic (", ddl, ")</strong>"),"<strong><em>p</em>-value</strong>","<strong><em>β</em><br>[95% CI]</strong>")), # Rename columnmodels =list(" "= est),notes = notes_txt,fmt =fmt_statistic(estimate =2, std.error =2, statistic =2, conf.low =2, conf.high =2, p.value = scales::label_pvalue()), ...)}en_modelsummary_lm(lm_1,coef_rename =c("especeJugulaire"="Species : Chinstrap","especePapou"="Species : Gentoo","sexeMâle"="Sex : Male"),vcov ="HC3")
Estimate [95% CI]
Standard error
t statistic (329)
p-value
β [95% CI]
Note : n = 333. Adjusted R² = 0.85. Standard errors : HC3.
(Intercept)
3372.39 [3315.89; 3428.88]
28.72
117.43
<0.001
-1.04 [-1.11; -0.97]
Species : Chinstrap
26.92 [-71.16; 125.01]
49.86
0.54
0.590
0.03 [-0.09; 0.16]
Species : Gentoo
1377.86 [1302.71; 1453.01]
38.20
36.07
<0.001
1.71 [1.62; 1.80]
Sex : Male
667.56 [598.91; 736.20]
34.90
19.13
<0.001
0.83 [0.74; 0.91]
Table 7: Régression de la masse des manchots en grammes, sur l’espèce et le sexe, selon l’Équation 1. Sortie en_modelsummary_lm().
Références
Arel-Bundock, V. (2022). modelsummary: Data and Model Summaries in R. Journal of Statistical Software, 103(1), 1‑23. https://doi.org/10.18637/jss.v103.i01
Bergé, L. R., Butts, K., & McDermott, G. (2026). Fast and user-friendly econometrics estimations: The R package fixest. https://arxiv.org/abs/2601.21749
Lüdecke, D., Ben-Shachar, M. S., Patil, I., & Makowski, D. (2020). Extracting, Computing and Exploring the Parameters of Statistical Models using R. Journal of Open Source Software, 5(53), 2445. https://doi.org/10.21105/joss.02445
Zeileis, A., Köll, S., & Graham, N. (2020). Various Versatile Variances: An Object-Oriented Implementation of Clustered Covariances in R. Journal of Statistical Software, 95(1), 1‑36. https://doi.org/10.18637/jss.v095.i01
---title: "Transformer une sortie `R` (moche) en table de résultats (belle) avec `modelsummary()`"date: 02-03-2026categories: ["R", "Modèle linéaire", "Fonction"]tags: ["Technique", "R"]description: "Dans un récent post, je proposais des fonctions basiques pour transformer une sortie `lm()` en table de résultats. D'autres ont déjà fait le travail avant moi, et bien mieux. C'est donc une bonne raison pour présenter le package `modelsummary`."lang: frbody-classes: indent-paragraphsformat: html: code-fold: true code-summary: "Voir le code" code-overflow: wrap code-tools: true code-block-bg: trueexecute: warning: false error: false---<hr style="border:1px solid blue" /># Point de départ```{r, echo=FALSE}#### Charger les packages ####library(tidyverse);library(modelsummary)# Charger les donnéesdf <- palmerpenguins::penguins %>% mutate( species = case_when( species == "Adelie" ~ "Adélie", species == "Chinstrap" ~ "Jugulaire", species == "Gentoo" ~ "Papou"), sex = case_when( sex == "female" ~ "Femelle", sex == "male" ~ "Mâle")) %>% # Traduire les données rename( masse = body_mass_g, espece = species, sexe = sex) # Renommer les colonnes```Dans [un récent billet](https://charlymarie.github.io/posts/03%20-%20lm_summary()/lm_summary().html){target='_blank'}, je montrais comment automatiser l'extraction de coefficients d'un modèle `lm()`. Je trouve que c'est toujours bien de travailler *à la main* : manipuler les données, comprendre comment elles sont construites, extraites, comment la table est organisée puis rendue, etc. Cela permet de comprendre ce qui se passe sous le capôt de la machine, et de gagner en autonomie si un package n'existe pas déjà pour faire ce que l'on souhaite faire. Mais, si l'on est honnête, mes fonctions étaient fragiles et la façon de faire peu élégante.Aussi, ces fonctions étaient bornées à `lm()`. Un `glm()`, `ordinal()`[@ordinal], ou autre `glm.nb()`[@MASS] auraient renvoyé une erreur, et il aurait fallu construire d'autres fonctions pour en extraire les résultats. Il se trouve que d'autres ont déjà fait ce travail de construction, notamment pour le package `modelsummary`[@modelsummary] !Je repars du même jeu de données `penguins`[@palmerpenguins] et du même modèle linéaire :$$\text{masse}_i= \alpha+ \beta_1\,X_{1i}+ \beta_2\,X_{2i}+ \beta_3\,X_{3i}+ \varepsilon_i$$ {#eq-un}où$$X_{1}=\mathbb{1}(\text{espèce}_i=\text{Jugulaire});\quadX_{2}=\mathbb{1}(\text{espèce}_i=\text{Papou});\quadX_{3}=\mathbb{1}(\text{sexe}_i=\text{Mâle}).$$A nouveau, la sortie en base `R` n'est pas très élégante.```{r, output=axis, echo=FALSE}#| label: tbl-regression-moche#| tbl-cap: "Régression de la masse des manchots en grammes, sur l'espèce et le sexe, selon l'@eq-un. Sortie base `R`."lm_1 <- lm(masse ~ espece + sexe, df)summary(lm_1)```# La magie de `modelsummary::modelsummary()``modelsummary()` va être très pratique pour un très grand nombre de raisons :- Supporte énormément de modèles, inférentiels ou bayésiens, des plus obscurs aux plus importants [comme `fixest`, @berge2026].- Directement combinable avec des erreurs personnalisées, du type robustes, clusterisées, etc, via `sandwich`[@sandwich].- Largement personnalisable, comme on va le voir.- Les tables sont élégantes.Dans sa version la plus basique, la fonction renvoie une table de régression comme on en voit tant.```{r}#| label: tbl-regression-modelsummary1#| tbl-cap: "Régression de la masse des manchots en grammes, sur l'espèce et le sexe, selon l'@eq-un. Sortie basique de `modelsummary()`."modelsummary(lm_1)```En soit, c'est tout ce dont on a besoin : à partir de l'estimation et de son erreur, on peut approximer *t*, *p*, et l'intervalle de confiance à 95 %. Mais, l'on pourrait tout de même souhaiter afficher les véritables statistiques plutôt que de devoir les approximer. J'ajoute donc les statistiques suivantes, alignées dans cet ordre dans la @tbl-regression-modelsummary2 :- Estimation.- Intervalles de confiance à 95 %.- Erreur standard.- Statistique *t*.- Statistique *p*.```{r}#| label: tbl-regression-modelsummary2#| tbl-cap: "Régression de la masse des manchots en grammes, sur l'espèce et le sexe, selon l'@eq-un. Sortie `modelsummary()` avec statistiques."modelsummary(lm_1,estimate =c("Estimation <br> [IC 95 %]"="{estimate}<br>[{conf.low}; {conf.high}]"), # Ajouter l'intervalle de confiance à l'estimatestatistic =c("Erreur standard"="std.error", "Statistique t"="statistic", "p-valeur"="p.value")) # Ajouter la statistique t et la p-valeur```Ici, ça se complique : les statistiques s'empilent, la table perd en lisibilité. On va devoir la faire pivoter, pour représenter les mêmes statistiques en colonne plutôt qu'en ligne.```{r}#| label: tbl-regression-modelsummary3#| tbl-cap: "Régression de la masse des manchots en grammes, sur l'espèce et le sexe, selon l'@eq-un. Sortie `modelsummary()` avec statistiques pivotées."modelsummary(lm_1,estimate =c("Estimation <br> [IC 95 %]"="{estimate}<br>[{conf.low}; {conf.high}]"),statistic =c("Erreur standard"="std.error", "Statistique t"="statistic", "p-valeur"="p.value"),shape = term ~ statistic) # Pivoter la table```Cette fois, la table commence à être exploitable. Je finalise l'aspect cosmétique, en :1. Renommant les coefficients.2. Retirant l'ensemble des indices d'ajustement, en renvoyant uniquement le nombre d'observations et le *R²* ajusté en une note de bas de tableau.3. Retirant le numéro de modèle (1).4. Mettant les titres de colonnes en *gras* et les statistiques en *italique*.5. Formattant les statistiques à deux décimales, et la *p*-valeur selon les normes APA.6. Ajoutant le nombre de degrés de liberté à la colonne *t*```{r}#| label: tbl-regression-modelsummary4#| tbl-cap: "Régression de la masse des manchots en grammes, sur l'espèce et le sexe, selon l'@eq-un. Sortie `modelsummary()` avec statistiques cosmétiques."g <- broom::glance(lm_1) # Stocker les indices d'ajustementddl <-unique(get_estimates(lm_1)$df.error) # Stocker les degrés de libertémodelsummary(shape = term ~ statistic,estimate =c("<strong>Estimation<br>[IC 95 %]</strong>"="{estimate}<br>[{conf.low}; {conf.high}]"), # <strong> pour mettre en gras et <em> pour mettre en italiquestatistic =setNames(c("std.error", "statistic", "p.value"),c("<strong>Erreur standard</strong>",paste0("<strong>Statistique <em>t</em> (", ddl, ")</strong>"), # On vient ici ajouter les degrés de liberté à la colonne"<strong><em>p</em>-valeur</strong>")), models =list(" "= lm_1), # Supprimer le (1)gof_omit =".*", # Supprimer les indices d'ajustementnotes = (paste0("<em>Note</em> : <em>n</em> = ", scales::number(g$nobs), " observations. <em>R²</em> ajusté = ", sprintf("%.2f", g$adj.r.squared),".")), # Ajouter les indices d'ajustement en note de bas de tableaucoef_rename =c("especeJugulaire"="Espèce : Jugulaire","especePapou"="Espèce : Papou","sexeMâle"="Sexe : Mâle"), # Renommer les coefficients fmt =fmt_statistic(estimate =2, std.error =2, statistic =2, conf.low =2, conf.high =2, p.value = scales::label_pvalue())) # Formatter les décimales```# Construire une fonction personnalisée et simple, regroupant tous les besoins classiques en psychologie : `fr_modelsummary_lm()`Il ne manque finalement qu'une chose à cette table : d'inclure un coefficient standardisé $\beta$. Cela demande un peu plus de travail car, de base, `modelsummary()` ne renvoie pas de coefficient standardisé. On va donc devoir (à nouveau) créer une table personnalisée avant de pouvoir l'envoyer à la fonction.Plutôt que de devoir à chaque fois ré-écrire ces scripts, et potentiellement devoir les mettre à jour en cas de changement, je construits une fonction pour (1) extraire une table personnalisée et (2) formater cette table personnalisée. Je m'appuie encore une fois sur le package `parameters`[@parameters] pour simplifier l'importation d'une matrice de variance-covariance facilement personnalisable^[Pour les personnes un peu plus geek, il serait aussi possible de [définir une fonction personnalisée et directement utilisable en interne de `modelsummary()`](https://modelsummary.com/vignettes/modelsummary_extension.html#modifying-information-tidy_custom-and-glance_custom){target='_blank'}, de type `tidy_custom`.].Comparées au post précédent, ces fonctions sont directement intégrées l'une à l'autre et permettent d'appeler une seule fonction personnalisée `fr_modelsummary_lm()` (ou son équivalent anglophone `en_modelsummary_lm()`, que je vous mets plus bas). Elles acceptent également un argument `vcov`. Finalement, elles seraient facilement personnalisables et adaptables à d'autres modèles avec quelques ajustements.```{r}# Fonction frfr_modelsummary_lm <-function( model, vcov =NULL,coef_rename =NULL, gof_omit =".*", ...) {# Vérifier que le modèle est bien de type lm()if (!inherits(model, "lm")) {stop("Le modèle fournit doit être un objet de classe lm. Ce n'est pas le cas ici.",call. =FALSE) }# Première fonction : Extraire une table custom_modelsummary_lm <-function(model, standardize ="refit", vcov =NULL, ...) { est <- modelsummary::get_estimates(model, vcov = vcov) # Extraire les coefficients pour la table de base est_std <- parameters::model_parameters(model, standardize = standardize, vcov = vcov) %>%# Extraire un coefficient standardisé dplyr::select(Parameter, Coefficient, CI_low, CI_high) %>%# Sélectionner les colonnes standardisées dplyr::rename(Std_Coefficient = Coefficient) # Renommer la colonne qui nous intéresse est$estimate_std <-paste0(sprintf("%.2f", est_std$Std_Coefficient), "<br>","[", sprintf("%.2f", est_std$CI_low), "; ", sprintf("%.2f", est_std$CI_high), "]") # Formatter les statistiques à deux décimales out <-list(tidy = est,glance = broom::glance(model)) # Stocker la table et les indices de fitclass(out) <-"modelsummary_list"# Définir la classe de l'objet de telle façon à ce que modelsummary() l'accepte out }# Seconde fonction : Formatter la table est <-custom_modelsummary_lm(model, vcov = vcov) # Appliquer la première fonction au modèle g <- broom::glance(model) # Stocker les indices d'ajustement ddl <-unique(modelsummary::get_estimates(model, vcov = vcov)$df.error) # Stocker les degrés de liberté vcov_note <-if (!is.null(vcov)) paste0(" Erreurs standards : <em>", vcov, "</em>.") else"" notes_txt <-paste0("<em>Note</em> : <em>n</em> = ", scales::number(g$nobs),". <em>R²</em> ajusté = ",sprintf("%.2f", g$adj.r.squared),".", vcov_note)modelsummary(coef_rename = coef_rename,gof_omit = gof_omit,shape = term ~ statistic, estimate =c("<strong>Estimation<br>[IC 95 %]</strong>"="{estimate}<br>[{conf.low}; {conf.high}]"),statistic =setNames(c("std.error", "statistic", "p.value", "estimate_std"), # Ajouter la colonne standardiséec("<strong>Erreur standard</strong>",paste0("<strong>Statistique <em>t</em> (", ddl, ")</strong>"),"<strong><em>p</em>-valeur</strong>","<strong><em>β</em><br>[IC 95 %]</strong>")), # Renommemodels =list(" "= est),notes = notes_txt,fmt =fmt_statistic(estimate =2, std.error =2, statistic =2, conf.low =2, conf.high =2, p.value = scales::label_pvalue()), ...)}```Ces fonctions personnalisables s'appuient ultimement sur `modelsummary()` et tout argument supplémentaire lui sera donc passé. Un exemple en français via `fr_modelsummary_lm()`, en intégrant une matrice de variance-covariance robuste à l'hétéroscédasticité.```{r}#| label: tbl-regression-modelsummary5#| tbl-cap: "Régression de la masse des manchots en grammes, sur l'espèce et le sexe, selon l'@eq-un. Sortie `fr_modelsummary_lm()`."fr_modelsummary_lm(lm_1,coef_rename =c("especeJugulaire"="Espèce : Jugulaire","especePapou"="Espèce : Papou","sexeMâle"="Sexe : Mâle"),vcov ="HC3")```# La fonction `en_modelsummary_lm()`Pour celles et ceux qui pourraient en avoir besoin en anglais, la fonction traduite et le même exemple en appelant cette fois la fonction `en_modelsummary_lm()` :```{r}#| label: tbl-regression-modelsummary6#| tbl-cap: "Régression de la masse des manchots en grammes, sur l'espèce et le sexe, selon l'@eq-un. Sortie `en_modelsummary_lm()`."# English functionen_modelsummary_lm <-function( model, vcov =NULL,coef_rename =NULL, gof_omit =".*", ...) {# Check that the model is of type lm()if (!inherits(model, "lm")) {stop("The model must be an object of class lm. This is not the case here.",call. =FALSE) }# First function: Extract a table custom_modelsummary_lm <-function(model, standardize ="refit", vcov =NULL, ...) { est <- modelsummary::get_estimates(model, vcov = vcov) # Extract coefficients for the base table est_std <- parameters::model_parameters(model, standardize = standardize, vcov = vcov) %>%# Extract standardised coefficients dplyr::select(Parameter, Coefficient, CI_low, CI_high) %>%# Select standardised columns dplyr::rename(Std_Coefficient = Coefficient) # Rename the column we are interested in est$estimate_std <-paste0(sprintf("%.2f", est_std$Std_Coefficient), "<br>","[", sprintf("%.2f", est_std$CI_low), "; ", sprintf("%.2f", est_std$CI_high), "]") # Format statistics to two decimal places out <-list(tidy = est,glance = broom::glance(model)) # Storing the table and fit indicesclass(out) <-"modelsummary_list"# Define the class of the object so that modelsummary() accepts it out }# Second function: Format the table est <-custom_modelsummary_lm(model, vcov = vcov) # Apply the first function to the model g <- broom::glance(model) # Stock fit indices ddl <-unique(modelsummary::get_estimates(model, vcov = vcov)$df.error) # Stock degrees of freedom vcov_note <-if (!is.null(vcov)) paste0(" Standard errors : <em>", vcov, "</em>.") else"" notes_txt <-paste0("<em>Note</em> : <em>n</em> = ", scales::number(g$nobs),". Adjusted <em>R²</em> = ", sprintf("%.2f", g$adj.r.squared),".", vcov_note)modelsummary(coef_rename = coef_rename,gof_omit = gof_omit,shape = term ~ statistic, estimate =c("<strong>Estimate<br>[95% CI]</strong>"="{estimate}<br>[{conf.low}; {conf.high}]"),statistic =setNames(c("std.error", "statistic", "p.value", "estimate_std"), # Add standardised columnc("<strong>Standard error</strong>",paste0("<strong><em>t</em> statistic (", ddl, ")</strong>"),"<strong><em>p</em>-value</strong>","<strong><em>β</em><br>[95% CI]</strong>")), # Rename columnmodels =list(" "= est),notes = notes_txt,fmt =fmt_statistic(estimate =2, std.error =2, statistic =2, conf.low =2, conf.high =2, p.value = scales::label_pvalue()), ...)}en_modelsummary_lm(lm_1,coef_rename =c("especeJugulaire"="Species : Chinstrap","especePapou"="Species : Gentoo","sexeMâle"="Sex : Male"),vcov ="HC3")```# Références